/*
 * jSquared
 * jSquared is a jQuery plug-in that enables ability to add/remove table columns and rows like in Google Squared UI.
 * @version 0.1
 * @author Vitaliy Fedorchenko http://code.google.com/p/nreco/wiki/JSquared
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
/*
 * This is almost a complete rewrite to fit the needs of Connie2, and
 * probably should have been started from scratch although it does
 * still owe a bit to jSquared.  Because of the customised
 * functionality and amount of change, classes etc and the plugin
 * itself have been renamed.
 */

(function($) {

  $.fn.connieFormGrid = function(settings) {
    settings = $.extend({}, $.fn.connieFormGrid.defaults, settings);

    var $cellTemplate;

    this.each(function() {
      init($(this));
    });

    function init(k) {
      var $t = k;
      $t.addClass('cfgTable');
      $t.find('tr').addClass('cfgRow');
      // Initialize existing column headers (styling, and removing old):)
      $t.find('tr:first').find('th:not(:first)').each(function() {
        var $h = $(this);
        var text = $h.html();
        var $newH = $( renderColumnHeader(text) );
        $newH.insertBefore( $h );
        $h.remove();
      });
      // set up cell template, so when we're adding and removing controls we don't inadvertently nest things:
      $cellTemplate = $(settings.newCellContents);
      if ($cellTemplate.length === 0)
        $cellTemplate = $('<span class="text">' + settings.newCellContents + '</span>');
      if (settings.wrapContents)
        $cellTemplate = $('<div class="cfgCellWrapper"></div>').append($cellTemplate);
      // Initialize cells (at least one row is expected to already be present):
      $t.find('tr:not(:first)').find('td').each(function() {
        var $cell = $(this);
        if (settings.wrapContents) {
          var wrapper = $('<div class="cfgCellWrapper"></div>').append($cell.children());
          $cell.append(wrapper);
        }
        maybeAddCellControls($cell);
      });

      // Add extra column - header (the "add new column" column):)
      $t.find('tr:first').children('th:last').after('<th class="cfgAddCol"><a href="javascript:void(0)"><h2>' +
                                                settings.addColumnText + '</h2></a></th>');
      // and fix existing rows!
      $t.find('tr:not(:first)').find('td:last').after('<td></td>');

      // Add handlers:
      $t.find('.cfgAddCol a').click( function() {
        onAddCol( $(this).parents('th:first') );
      });
      $t.find('.cfgColumnRemove').live('click', function() {
        onRemoveCol( $(this).parents('th:first') );
      });
      $t.find('.cfgAddCell').live('click', function() {
        newCellAfter( $(this).parents('td:first') );
      });
      $t.find('.cfgRemoveCell').live('click', function() {
        removeThisCell( $(this).parents('td:first') );
      });
    }

    function renderColumnHeader(header) {
      var $header = $(header).clone(); // clone in case they have passed a DOM element instead of text
      if ($header.length === 0)
        $header =  $('<span class="text">'+header+'</span>');
      if (settings.wrapContents) {
        var $contents = $('<div class="cfgHeaderWrapper"></div>')
          .append($header)
          .append('<a class="cfgColumnRemove" href="javascript:void(0)"><h2>'+settings.removeColumnText+'</h2></a>');
        $header = $('<th class="cfgColumnText"></th>').append($contents);
      }
      else {
        $header = $('<th class="cfgColumnText"></th>').append($header)
          .append('<a class="cfgColumnRemove" href="javascript:void(0)"><h2>'+settings.removeColumnText+'</h2></a>');
      }
      return $header;
    }
    function onAddCol(cell) {
      var $t = cell.parents('table.cfgTable:first');
      var thIndex = cell.parents('tr.cfgRow:first').children('th').index(cell);
      // render col header
      var $newTh = renderColumnHeader(settings.headerContents);
      $newTh.insertBefore( cell.parents('tr.cfgRow:first').children('th').eq(thIndex) );
      // render cells
      $t.find('tr:not(:first)').each(function() {
        $(this).children('td').eq(thIndex).before('<td></td>');
      });
      // Remember to clone the template!
      var $newcellcontents = $cellTemplate.clone();
      var $controls = $('<span class="cfgCellControls"><a class="cfgAddCell" href="javascript:void(0)"><h3>'
                        + settings.addRowText
                        + '</h3></a></span>');
      var $newcell = $t.find('tr:eq(1)').find('td').eq(thIndex);
      $newcell.append($cellTemplate.clone());
      maybeAddCellControls($newcell);
      settings.onColumnAdded($newTh, $t.find('tr').find('td:eq('+thIndex+')'));
    }
    function onRemoveCol(cell) {
      var $t = cell.parents('table.cfgTable:first');
      var idx = cell.parents('tr.cfgRow:first').find('th').index(cell);
      var $colcells = $t.find('tr:not(:first)').find('td:eq('+idx+')');
      settings.onColumnRemoving(cell, $colcells);
      $t.find('tr:first').find('th').eq(idx).remove();
      $colcells.remove();
      // clean up any empty rows that might result:
      $t.find('tr:not(:first)').each(function() {
        var $row = $(this);
        if (rowIsEmpty($row))
          $row.remove();
      });
      settings.onColumnRemoved();
    }

    function newCellAfter(cell) {
      var $row = cell.parents('tr.cfgRow:first');
      // defensive programming; only add another if we're not already on the limit:
      var rowIdx = $row.parents('table.cfgTable:first').find('tr.cfgRow').index($row);
      if (settings.maxRows <= 1 || rowIdx < settings.maxRows) {
        // First, is this the last table row?  If so, we need to create a new one:
        var $nextRow = $row.next('tr');
        if ($nextRow.length === 0) {
          var cells = '';
          for (var i = 0; i < $row.find('td').length; i++)
            cells += '<td></td>';
          $nextRow = $('<tr class="cfgRow">' + cells + '</tr>');
          $row.after($nextRow);
        }
        cell.find('.cfgCellControls').remove();
        var $newCell = $nextRow.find('td').eq( $row.find('td').index(cell) );
        // note: settings.newCellContents might be a DOM element, so we clone just in case:
        $newCell.append($cellTemplate.clone());
        maybeAddCellControls($newCell);
        settings.onCellAdded($newCell);
      }
    }
    function removeThisCell(cell) {
      settings.onCellRemoving(cell);
      cell.html('');            // .children().remove() doesn't clear text
      var $row = cell.parents('tr.cfgRow:first');
      var colIdx = $row.find('td').index(cell);
      // tricky bit of state here; need to remove row before fixing up previous cell:
      var $prevCell = $row.prev('tr').find('td').eq(colIdx);
      if (rowIsEmpty($row))
        $row.remove();
      maybeAddCellControls($prevCell);
      settings.onCellRemoved();
    }
    function maybeAddCellControls(cell) {
      if (cellIsLastInColumn(cell)) {
        var $controls = $('<span class="cfgCellControls"></span>');
        if (settings.wrapContents)
          cell.find('.cfgCellWrapper').append($controls);
        else
          cell.append('<span class="cfgCellControls"></span>');
        var $row = cell.parents('tr.cfgRow:first');
        var rowIdx = $row.parents('table.cfgTable:first').find('tr.cfgRow').index($row);
        if (settings.maxRows <= 0 || rowIdx < settings.maxRows - 1)
          $controls.append('<a class="cfgAddCell" href="javascript:void(0)"><h3>'
                           + settings.addRowText
                           + '</h3></a>');
        if (rowIdx !== 1)
          $controls.append($('<a class="cfgRemoveCell" href="javascript:void(0)"><h3>'
                             + settings.removeCellText
                             + '</h3></a>'));
      }
    }

    function cellIsEmpty(cell) {
      return cell.children().length === 0 && cell.text() == '';
    }
    function rowIsEmpty(row) {
      var cells = row.find('td');
      return cells.children().length === 0 && cells.text() == '';
    }
    function cellIsLastInColumn(cell) {
      var $row = cell.parents('tr.cfgRow:first');
      var colIndex = $row.find('td').index(cell);
      var isLast = true;
      $row.nextAll('tr.cfgRow').find('td:eq('+colIndex+')').each(function() {
        isLast = isLast && cellIsEmpty($(this));
      });
      return isLast;
    }

    return this;
  };

  //Default configuration:
  $.fn.connieFormGrid.defaults = {
    addColumnText: 'Add',
    addRowText: 'Add',
    removeCellText: 'Remove',
    removeColumnText: '[x]',
    headerContents: 'Header',
    newCellContents: 'vertical level',
    maxRows: 0,                 // unbounded if <= 1
    wrapContents: true,         // if true, all header/cell contents, including controls, are wrapped in a div.
    onCellAdding: function() { }, // called before adding
    onCellAdded: function(cell) { }, // called after adding
    onCellRemoving: function(cell) { }, // called before anything is removed
    onCellRemoved: function() { }, // called after adding
    onColumnAdding: function() { }, // called before adding
    onColumnAdded: function(headerCell, dataCells) { }, //called after adding
    onColumnRemoving: function(headerCell, dataCells) { }, // called before anything is removed
    onColumnRemoved: function() { } // called after adding
  };

  //Current version:
  $.fn.connieFormGrid.version = 0.2;

 })(jQuery);
